﻿using System.Globalization;
using Windows.ApplicationModel.DataTransfer;
using Windows.Globalization.NumberFormatting;
using Windows.Storage;
using Microsoft.Web.WebView2.Core;

namespace DimensionsHelper.Client.WinUi.Views;

public sealed partial class MetadataDocumentPage : Page
{
    private const string MdmEditorFontSizeKey = "MdmEditorFontSize";
    private readonly string _htmlPath;
    private double _fontSize;


    public MetadataDocumentPage()
    {
        ViewModel = App.GetService<MetadataDocumentViewModel>();
        InitializeComponent();
        UpdateMenuButtons();

        InitHistory();

        IndentNumberBox.NumberFormatter = new DecimalFormatter
        {
            IntegerDigits = 1,
            FractionDigits = 0,
            IsDecimalPointAlwaysDisplayed = false,
            NumberRounder = new IncrementNumberRounder
            {
                Increment = 1,
                RoundingAlgorithm = RoundingAlgorithm.RoundHalfUp
            }
        };

        _fontSize = App.GetAppDataSettings(MdmEditorFontSizeKey, 14d);
        _htmlPath = Path.Combine(AppContext.BaseDirectory, @"ThirdParty\monaco-editor\index.html");

        Loaded += OnLoaded;

        CodeEditorWebView.CoreWebView2Initialized += OnCoreWebView2Initialized;
        ViewModel.HistoryAdded += OnViewModelHistoryAdded;
        ViewModel.HistoryCleared += OnViewModelHistoryCleared;

        ViewModel.DocumentLoaded += OnDocumentLoaded;
        ViewModel.PropertyChanged += OnViewModelPropertyChanged;
    }


    public MetadataDocumentViewModel ViewModel
    {
        get;
    }


    private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(MetadataDocumentViewModel.SerializedContent))
        {
            SetEditorText(ViewModel.SerializedContent);
        }
    }


    private async void OnLoaded(object sender, RoutedEventArgs e)
    {
        try
        {
            await CodeEditorWebView.EnsureCoreWebView2Async();
            RefreshEditorTheme();

            UriBuilder builder = new()
            {
                Scheme = Uri.UriSchemeFile,
                Path = _htmlPath
            };

            CodeEditorWebView.CoreWebView2.Navigate(builder.Uri.AbsoluteUri);
        }
        catch (Exception exception)
        {
            await App.ShowErrorMessage(exception);
        }
    }


    private void OnCoreWebView2Initialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
    {
        CoreWebView2Settings? settings = sender.CoreWebView2.Settings;
        settings.IsBuiltInErrorPageEnabled = false;
        settings.IsStatusBarEnabled = false;
        settings.IsWebMessageEnabled = false;
        settings.AreHostObjectsAllowed = true;
        settings.AreBrowserAcceleratorKeysEnabled = false;

        sender.CoreWebView2.NavigationCompleted += OnCoreWebView2NavigationCompleted;
        sender.CoreWebView2.Navigate(_htmlPath);

        ActualThemeChanged += OnActualThemeChanged;
    }


    private async void OnCoreWebView2NavigationCompleted(CoreWebView2 sender,
        CoreWebView2NavigationCompletedEventArgs e)
    {
        try
        {
            RefreshEditorTheme();
            await sender.ExecuteScriptAsync("initializeMonacoEditor();");
            // 禁用WebView2的拖拽事件
            await sender.ExecuteScriptAsync(
                "document.addEventListener('dragover', e => e.preventDefault());" +
                "document.addEventListener('drop', e => e.preventDefault());");
            SetEditorText(ViewModel.SerializedContent);
            SetEditorFontSize(_fontSize);
            CodeEditorWebView.Visibility = Visibility.Visible;
        }
        catch (Exception exception)
        {
            await App.ShowErrorMessage(exception);
        }
    }


    private void OnActualThemeChanged(FrameworkElement sender, object args) => RefreshEditorTheme();


    private async void RefreshEditorTheme()
    {
        try
        {
            string themeName = ActualTheme == ElementTheme.Light ? "vs" : "vs-dark";
            await CodeEditorWebView.ExecuteScriptAsync($"monaco.editor.setTheme(\"{themeName}\")");
        }
        catch (Exception e)
        {
            await App.ShowErrorMessage(e);
        }
    }


    private async void SetEditorFontSize(double size)
    {
        try
        {
            await CodeEditorWebView.CoreWebView2.ExecuteScriptAsync($"editor.updateOptions({{ fontSize: {size} }})");
        }
        catch (Exception e)
        {
            await App.ShowErrorMessage(e);
        }
    }


    private void OnIncreaseFontSizeButtonClick(object sender, RoutedEventArgs e)
    {
        SetEditorFontSize(++_fontSize);
        App.SetAppDataSettings(MdmEditorFontSizeKey, _fontSize);
    }


    private void OnDecreaseFontSizeButtonClick(object sender, RoutedEventArgs e)
    {
        if (_fontSize <= 10)
        {
            return;
        }

        SetEditorFontSize(--_fontSize);
        App.SetAppDataSettings(MdmEditorFontSizeKey, _fontSize);
    }


    private async void SetEditorText(string text)
    {
        try
        {
            text = text.Replace("\\", @"\\")
                .Replace("\n", @"\n")
                .Replace("\r", @"\r")
                .Replace("\t", @"\t")
                .Replace("\b", @"\b")
                .Replace("\f", @"\f")
                .Replace("\"", "\\\"");

            await CodeEditorWebView.CoreWebView2.ExecuteScriptAsync($"editor.setValue(\"{text}\");");
        }
        catch (Exception e)
        {
            await App.ShowErrorMessage(e);
        }
    }


    private async Task<string> GetEditorText()
    {
        string text = await CodeEditorWebView.CoreWebView2.ExecuteScriptAsync("editor.getValue();");

        // 将转义过的转义字符替换为转义字符
        text = text.Replace(@"\\", "\\")
            .Replace(@"\n", "\n")
            .Replace(@"\r", "\r")
            .Replace(@"\t", "\t")
            .Replace(@"\b", "\b")
            .Replace(@"\f", "\f")
            .Replace("\\\"", "\"");

        // Unicode
        text = UnicodeRegex().Replace(text, m =>
        {
            int unicode = int.Parse(m.Value.Replace("\\u", ""), NumberStyles.HexNumber);
            char c = (char)unicode;
            return c.ToString();
        });

        return text;
    }


    [RelayCommand]
    private async Task SaveFile()
    {
        string? file = await App.GetService<IFilePikerService>().SaveFileAsync(FileFilters.Dms);

        if (string.IsNullOrEmpty(file))
        {
            return;
        }

        try
        {
            string text = await GetEditorText();
            await File.WriteAllTextAsync(file, text);
            MetadataDocumentViewModel.GetProjectTask()
                .Log(ResourceKeys.AppFileExportSuccessfullyMessage.GetLocalized(), file);
        }
        catch (Exception)
        {
            // ignored
        }
    }


    private void OnViewModelHistoryCleared() => HistoryMenuItem.Items.Clear();


    private void RemoveTopOfHistoricalFiles(int count)
    {
        if (count <= 0 || HistoryMenuItem.Items.Count == 0)
        {
            return;
        }

        while (count > 0 && HistoryMenuItem.Items.Count > 0)
        {
            HistoryMenuItem.Items.RemoveAt(0);
            count--;
        }
    }


    private void OnViewModelHistoryAdded(OptionalFileViewModel viewModel)
    {
        int max = App.HistoryKeptNumber;
        RemoveTopOfHistoricalFiles(HistoryMenuItem.Items.Count + 1 - max);
        AddHistory(viewModel);
    }


    private void AddHistory(OptionalFileViewModel fileViewModel)
    {
        MenuFlyoutItem item = new()
        {
            Text = fileViewModel.FileName
        };

        item.Click += (_, _) =>
        {
            fileViewModel.IsSelected = true;
        };

        HistoryMenuItem.Items.Add(item);
    }


    private void InitHistory()
    {
        foreach (OptionalFileViewModel fileViewModel in ViewModel.HistoricalList)
        {
            AddHistory(fileViewModel);
        }
    }


    [GeneratedRegex(@"\\u[A-Z0-9]{4}")]
    private static partial Regex UnicodeRegex();


    private static void SelectRadioMenuFlyoutItem(MenuFlyout flyout, string label)
    {
        RadioMenuFlyoutItem? item = flyout
            .Items
            .FirstOrDefault(item =>
                item is RadioMenuFlyoutItem r &&
                r.Text.Equals(label, StringComparison.OrdinalIgnoreCase)) as RadioMenuFlyoutItem;

        if (item != null)
        {
            item.IsChecked = true;
        }
    }


    private static void AddRadioMenuFlyoutItem(MenuFlyout flyout, string label, Action<string> onClick,
        string groupName)
    {
        if (string.IsNullOrEmpty(label) ||
            flyout.Items.Any(
                i => i is RadioMenuFlyoutItem r && r.Text.Equals(label, StringComparison.OrdinalIgnoreCase)))
        {
            return;
        }

        RadioMenuFlyoutItem item = new()
        {
            Text = label,
            GroupName = groupName
        };

        item.Click += (o, _) => onClick(((RadioMenuFlyoutItem)o).Text);
        flyout.Items.Add(item);
    }


    private static void RefreshMenuButtons(ObservableCollection<string> list, MenuFlyout target, Action<string> setter,
        string groupName)
    {
        target.Items.Clear();

        foreach (string label in list)
        {
            AddRadioMenuFlyoutItem(target, label, setter, groupName);
        }
    }


    private void UpdateMenuButtons()
    {
        RefreshMenuButtons(ViewModel.Contexts, ContextMenuFlyout,
            c => ViewModel.Context = c, "DocumentContexts");
        RefreshMenuButtons(ViewModel.Languages, LanguageMenuFlyout,
            l => ViewModel.Language = l, "DocumentLanguages");
        RefreshMenuButtons(ViewModel.LabelTypes, LabelTypeMenuFlyout,
            l => ViewModel.LabelType = l, "DocumentLabelTypes");
        // 选取当前值
        SelectRadioMenuFlyoutItem(ContextMenuFlyout, ViewModel.Context);
        SelectRadioMenuFlyoutItem(LanguageMenuFlyout, ViewModel.Language);
        SelectRadioMenuFlyoutItem(LabelTypeMenuFlyout, ViewModel.LabelType);
    }


    private void OnDocumentLoaded(object? sender, MetadataDocumentLoadedEventArgs e)
    {
        // 如果IsReload不是true,需要更新上下文列表
        if (!e.IsReload)
        {
            UpdateMenuButtons();
        }

        SetEditorText(e.Content);
    }


    private async void WebView_DragOver(object sender, DragEventArgs e)
    {
        try
        {
            if (!e.DataView.Contains(StandardDataFormats.StorageItems))
            {
                return;
            }

            IReadOnlyList<IStorageItem> storages = await e.DataView.GetStorageItemsAsync();
            IStorageItem? firstMdm = storages.FirstOrDefault(s =>
                Path.GetExtension(s.Path).Equals(".mdd", StringComparison.OrdinalIgnoreCase));

            if (firstMdm == null)
            {
                return;
            }

            e.AcceptedOperation = DataPackageOperation.Copy;
            e.Data ??= new DataPackage();
            e.Data.SetText(firstMdm.Path);
            e.Handled = true;
        }
        catch (Exception exception)
        {
            await App.ShowErrorMessage(exception);
        }
    }

    private async void WebView_Drop(object sender, DragEventArgs e)
    {
        try
        {
            IReadOnlyList<IStorageItem> storages = await e.DataView.GetStorageItemsAsync();
            IStorageItem? firstMdm = storages.FirstOrDefault(s =>
                Path.GetExtension(s.Path).Equals(".mdd", StringComparison.OrdinalIgnoreCase));

            if (!string.IsNullOrEmpty(firstMdm?.Path))
            {
                ViewModel.CurrentFileName = firstMdm.Path;
            }
        }
        catch (Exception exception)
        {
            await App.ShowErrorMessage(exception);
        }
    }
}